package com.nx.common.tools;

import com.nx.common.result.HttpClientResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.MessageConstraints;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.nio.charset.CodingErrorAction;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author linhongwei
 * @version 1.0.0
 * @ClassName HttpClientUtil.java
 * @Description http client工具类
 */
@Slf4j
public final class HttpClientUtil {
    /**
     * 默认连接超时时间，单位ms
     */
    public static final int CONNECT_TIME_OUT = 6 * 1000;
    /**
     * 默认读取响应超时时间，单位ms
     */
    public static final int READ_TIME_OUT = 6 * 1000;
    /**
     * 默认字符编码
     */
    public static final String CHARSET = "UTF-8";
    /**
     * 默认mime类型
     */
    public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
    public static final String APPLICATION_JSON_VALUE = "application/json";
    /**
     * 全局HttpClient
     */
    private static HttpClient client = null;

    static {
        // 创建连接池管理器
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();

        // 创建SocketConfig配置, 主要是建立连接后，通信双方的配置，其实就是我们认识的客户端与服务端的套接字(Socket)通信
        SocketConfig socketConfig = SocketConfig.custom()
                .setTcpNoDelay(true)
                .setBacklogSize(10240)
                .setSndBufSize(10000)
                .setRcvBufSize(10000)
                .setSoKeepAlive(true)
                .setSoLinger(10000)
                .setSoReuseAddress(true)
                .setSoTimeout(10000)
                .build();
        connManager.setDefaultSocketConfig(socketConfig);

        //检测不活跃连接的有效性时间
        connManager.setValidateAfterInactivity(10000);

        // 消息报文约束
        MessageConstraints messageConstraints = MessageConstraints.custom()
                .setMaxHeaderCount(200)
                .setMaxLineLength(20000)
                .build();

        // 创建连接配置
        ConnectionConfig connectionConfig = ConnectionConfig.custom()
                .setMalformedInputAction(CodingErrorAction.IGNORE)
                .setUnmappableInputAction(CodingErrorAction.IGNORE)
                .setCharset(Consts.UTF_8)
                .setMessageConstraints(messageConstraints)
                .build();
        connManager.setDefaultConnectionConfig(connectionConfig);

        //配置最大的连接数
        connManager.setMaxTotal(100);
        //配合路由的最大连接数
        connManager.setDefaultMaxPerRoute(10);
        //创建http客户端
        client = HttpClients.custom().setConnectionManager(connManager).build();
    }

    /**
     * 描述: http or https post请求（body体参数）
     *
     * @param url          请求链接
     * @param parameterStr body体参数json的string格式
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult sendPost(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, IOException {
        return post(url, parameterStr, APPLICATION_JSON_VALUE, CHARSET, null, CONNECT_TIME_OUT, READ_TIME_OUT);
    }

    /**
     * 描述: http or https post请求（body体参数）
     *
     * @param url          请求链接
     * @param parameterStr body体参数json的string格式
     * @param mimeType     消息类型，如application/json
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult sendPost(String url, String parameterStr, String mimeType) throws ConnectTimeoutException, SocketTimeoutException, IOException {
        return post(url, parameterStr, mimeType, CHARSET, null, CONNECT_TIME_OUT, READ_TIME_OUT);
    }

    /**
     * 描述: http or https post请求（body体参数）
     *
     * @param url          请求链接
     * @param parameterStr body体参数json的string格式
     * @param mimeType     消息类型
     * @param charset      字符编码
     * @param headers      请求头部参数
     * @param connTimeout  连接超时时间
     * @param readTimeout  响应超时时间
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult sendPost(String url, String parameterStr, String mimeType, String charset, Map<String, String> headers, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException,
            SocketTimeoutException, IOException {
        return post(url, parameterStr, mimeType, charset, headers, connTimeout, readTimeout);
    }

    /**
     * 描述: http or https post请求（form表单）
     *
     * @param url    请求链接
     * @param params form表单参数
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult sendFormPost(String url, Map<String, String> params) throws ConnectTimeoutException,
            SocketTimeoutException, IOException {
        return postForm(url, params, null, CONNECT_TIME_OUT, READ_TIME_OUT);
    }

    /**
     * 描述: http or https post请求（form表单）
     *
     * @param url     请求链接
     * @param params  form表单参数
     * @param headers 请求头部参数
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult sendFormPost(String url, Map<String, String> params, Map<String, String> headers) throws ConnectTimeoutException,
            SocketTimeoutException, Exception {
        return postForm(url, params, headers, CONNECT_TIME_OUT, READ_TIME_OUT);
    }

    /**
     * 描述: http or https post请求（form表单）
     *
     * @param url         请求链接
     * @param params      form表单参数
     * @param headers     请求头部参数
     * @param connTimeout 连接超时时间
     * @param readTimeout 响应超时时间
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult sendFormPost(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException,
            SocketTimeoutException, IOException {
        return postForm(url, params, headers, connTimeout, readTimeout);
    }

    /**
     * 描述: http or https get请求（不指定字符编码）
     *
     * @param url 请求链接
     * @return
     * @throws Exception
     */
    public static HttpClientResult sendGet(String url) throws IOException {
        return get(url, CHARSET, null, null, null);
    }

    /**
     * 描述: http or https get请求（指定字符编码）
     *
     * @param url     请求链接
     * @param charset 字符编码
     * @return
     * @throws Exception
     */
    public static HttpClientResult sendGet(String url, String charset) throws IOException {
        return get(url, charset, null, CONNECT_TIME_OUT, READ_TIME_OUT);
    }

    /**
     * 描述: http or https get请求 (指定请求头)
     *
     * @param url     请求链接
     * @param charset 字符编码
     * @param headers 请求头键值对
     * @return
     * @throws Exception
     */
    public static HttpClientResult sendGet(String url, String charset, Map<String, String> headers) throws IOException {
        return get(url, charset, headers, CONNECT_TIME_OUT, READ_TIME_OUT);
    }

    /**
     * 描述: http or https get请求（指定字符编码和超时等待时间）
     *
     * @param url         请求链接
     * @param charset     字符编码
     * @param headers     请求头键值对
     * @param connTimeout 连接超时时间
     * @param readTimeout 响应超时时间
     * @return
     * @throws Exception
     */
    public static HttpClientResult sendGet(String url, String charset, Map<String, String> headers, Integer connTimeout, Integer readTimeout) throws IOException {
        return get(url, charset, headers, connTimeout, readTimeout);
    }

//    public static String sendPut(String url, Map<String, String> headers, String jsonStr) {
//
//        CloseableHttpClient httpClient = HttpClients.createDefault();
//        HttpPut httpPut = new HttpPut(url);
//        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000).setConnectionRequestTimeout(35000).setSocketTimeout(60000).build();
//        httpPut.setConfig(requestConfig);
//        httpPut.setHeader("Content-type", "application/json");
//        httpPut.setHeader("DataEncoding", "UTF-8");
//        httpPut.setHeader("token", token);
//
//        CloseableHttpResponse httpResponse = null;
//        try {
//            httpPut.setEntity(new StringEntity(jsonStr));
//            httpResponse = httpClient.execute(httpPut);
//            HttpEntity entity = httpResponse.getEntity();
//            String result = EntityUtils.toString(entity);
//            return result;
//        } catch (ClientProtocolException e) {
//            // TODO Auto-generated catch block
//            e.printStackTrace();
//        } catch (IOException e) {
//            // TODO Auto-generated catch block
//            e.printStackTrace();
//        } finally {
//            if (httpResponse != null) {
//                try {
//                    httpResponse.close();
//                } catch (IOException e) {
//                    // TODO Auto-generated catch block
//                    e.printStackTrace();
//                }
//            }
//            if (null != httpClient) {
//                try {
//                    httpClient.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//        }
//        return null;
//    }
    public static HttpClientResult sendPut(String url, String body, String mimeType, String charset, Map<String, String> headers) throws IOException {
        HttpPut put = new HttpPut(url);
        HttpClientResult result = null;
//        log.info("post url: {}", url);
//        log.info("post body: {}", body);
//        log.info("post headers: {}", headers);
        try {
            if (body != null && body.trim() != "") {
                HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
                put.setEntity(entity);
            }
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    put.addHeader(entry.getKey(), entry.getValue());
                }
            }
            //设置配置参数
            RequestConfig.Builder customReqConf = RequestConfig.custom();
            customReqConf.setConnectTimeout(CONNECT_TIME_OUT * 1000);
            customReqConf.setConnectionRequestTimeout(CONNECT_TIME_OUT * 1000);
            customReqConf.setRedirectsEnabled(true);
            put.setConfig(customReqConf.build());
            HttpResponse response = client.execute(put);
            result = responseToResult(response);
        } finally {
            put.releaseConnection();
        }
        return result;
    }
    /**
     * 描述: http or https post请求处理方法(requestBody传参)
     *
     * @param url--请求链接
     * @param body--RequestBody
     * @param mimeType--消息类型：例如     application/xml "application/x-www-form-urlencoded"
     * @param charset--字符编码
     * @param headers--请求头键值对
     * @param connTimeout--连接超时时间
     * @param readTimeout--读取响应超时时间
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult post(String url, String body, String mimeType, String charset, Map<String, String> headers, Integer connTimeout, Integer readTimeout) throws IOException {
        HttpPost post = new HttpPost(url);
        HttpClientResult result = null;
//        log.info("post url: {}", url);
//        log.info("post body: {}", body);
//        log.info("post headers: {}", headers);
        try {
            if (body != null && body.trim() != "") {
                HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
                post.setEntity(entity);
            }
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    post.addHeader(entry.getKey(), entry.getValue());
                }
            }
            //设置配置参数
            RequestConfig.Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
                customReqConf.setConnectionRequestTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            customReqConf.setRedirectsEnabled(true);
            post.setConfig(customReqConf.build());
            HttpResponse response = client.execute(post);
            result = responseToResult(response);
        } finally {
            post.releaseConnection();
        }
        return result;
    }

    public static HttpClientResult post(String url, String body, String mimeType, Map<String, String> headers) throws IOException {
        return post(url, body, mimeType, CHARSET, headers, CONNECT_TIME_OUT * 1000, READ_TIME_OUT * 1000);
    }

    /**
     * 描述: http or https post请求处理方法(form表单传参)
     *
     * @param url--请求链接
     * @param params--参数键值对
     * @param headers--请求头键值对
     * @param connTimeout--连接超时时间
     * @param readTimeout--响应超时时间
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout, Integer readTimeout) throws IOException {
        HttpClientResult result = null;
        HttpPost post = new HttpPost(url);
        try {
            if (params != null && !params.isEmpty()) {
                List<NameValuePair> formParams = new ArrayList();
                Set<Map.Entry<String, String>> entrySet = params.entrySet();
                for (Map.Entry<String, String> entry : entrySet) {
                    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
                post.setEntity(entity);
            }
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    post.addHeader(entry.getKey(), entry.getValue());
                }
            }
            // 设置参数
            RequestConfig.Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            post.setConfig(customReqConf.build());
            result = responseToResult(client.execute(post));
        } finally {
            post.releaseConnection();
        }
        return result;
    }

    /**
     * 描述: http or https get请求处理方法
     *
     * @param url--请求链接
     * @param charset--字符编码
     * @param headers--请求头键值对
     * @param connTimeout--连接超时时间
     * @param readTimeout--响应超时时间
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static HttpClientResult get(String url, String charset, Map<String, String> headers, Integer connTimeout, Integer readTimeout) throws IOException {
        HttpGet get = new HttpGet(url);
        HttpClientResult result = null;
        if (headers != null && !headers.isEmpty()) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                get.addHeader(entry.getKey(), entry.getValue());
            }
        }
        try {
            // 设置参数
            RequestConfig.Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            get.setConfig(customReqConf.build());
            result = responseToResult(client.execute(get));
        } finally {
            get.releaseConnection();
        }
        return result;
    }

    /**
     * 描述: http or https 文件中转上传
     *
     * @param serverUrl        上传地址
     * @param originalFileName 待上传文件名
     * @param bits             待上传文件二进制数组
     * @param uploadFieldName  上传后的文件名
     * @param params           其他的请求参数
     * @return
     * @throws IOException
     */
    public static HttpClientResult httpClientUpload(String serverUrl, String originalFileName, byte[] bits, String uploadFieldName, Map<String, String> params) throws IOException {
        HttpClientResult result = null;
        // 请求处理url
        HttpPost post = new HttpPost(serverUrl);
        try {
            // 创建待处理的文件
            ContentBody files = new ByteArrayBody(bits, originalFileName);
            // 对请求的表单域进行填充
            MultipartEntity reqEntity = new MultipartEntity();
            reqEntity.addPart(uploadFieldName, files);
            if (params != null && !params.isEmpty()) {
                for (Map.Entry<String, String> item : params.entrySet()) {
                    reqEntity.addPart(
                            item.getKey(),
                            new StringBody(URLEncoder.encode(
                                    String.valueOf(item.getValue()), CHARSET)));
                }
            }
            // 设置请求
            post.setEntity(reqEntity);
            result = responseToResult(client.execute(post));
        } finally {
            post.releaseConnection();
        }
        return result;
    }

    /**
     * 描述: get请求url参数拼接
     *
     * @param url    请求链接
     * @param params 请求参数
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String concatGetUrl(String url, Map<String, Object> params) throws UnsupportedEncodingException {
        StringBuffer buf = new StringBuffer(url);
        if (params != null) {
            if (!url.contains("?")) {
                buf.append("?");
            }
            for (Map.Entry<String, Object> item : params.entrySet()) {
                buf.append("&" + item.getKey() + "=" + URLEncoder.encode(String.valueOf(item.getValue()), "UTF-8"));
            }
        }
        return buf.toString();
    }

    public static HttpClientResult sendDel(String url, String charset, Map<String, String> headers) throws IOException {
        HttpDelete delete = new HttpDelete(url);
        HttpClientResult result = null;
        if (headers != null && !headers.isEmpty()) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                delete.addHeader(entry.getKey(), entry.getValue());
            }
        }
        try {
            // 设置参数
            RequestConfig.Builder customReqConf = RequestConfig.custom();
            customReqConf.setConnectTimeout(CONNECT_TIME_OUT);
            customReqConf.setSocketTimeout(READ_TIME_OUT);

            delete.setConfig(customReqConf.build());
            result = responseToResult(client.execute(delete));
        } finally {
            delete.releaseConnection();
        }
        return result;
    }

    /**
     * 处理http响应
     *
     * @param res
     * @return
     */
    private static HttpClientResult responseToResult(HttpResponse res) throws IOException {
        HttpClientResult result = new HttpClientResult();
        result.setCode(res.getStatusLine().getStatusCode());
        result.setReasonPhrase(res.getStatusLine().getReasonPhrase());
//        if (HttpStatus.SC_OK == res.getStatusLine().getStatusCode()) {
//            HttpEntity entity = res.getEntity();
//            result.setContent(EntityUtils.toString(entity, Consts.UTF_8));
//        }
        HttpEntity entity = res.getEntity();
        if (CheckTools.isNotNullOrEmpty(entity)) {
            result.setContent(EntityUtils.toString(entity, Consts.UTF_8));
        }
        Header[] headers = res.getAllHeaders();
        if (CheckTools.isNotNullOrEmpty(headers)) {
            for (Header header : headers) {
                result.getResponseHeaders().put(header.getName(), header.getValue());
            }
        }
        return result;
    }
}